import json
import time
import cv2
from cv2.typing import MatLike
import numpy as np

from domains.Console import Console
from domains.Template import Template
from domains.typing import TemplateLike
from domains.Exception import TemplateNotFoundError, NotLoggedInError
from domains.Notifier import Notifier
from utils.funcs import get_success_msg, get_fail_msg, get_developer_msg
from utils.lg import logger


class Checker:
    def __init__(self, console: Console, notifier: Notifier):
        self._console = console
        self._console_size = [self._console.width, self._console.height]
        self._tm_threshold = 0.8
        self._notifier = notifier
        with open(r"data\settings.json", 'r', encoding='utf-8') as f:
            settings = json.load(f)['Checker']
        self._email_list: list[str] = settings['email_list']
        self._main_email: str = settings['main_email']

    def _make_screenshot(self) -> MatLike:
        """
        包装截屏操作，更新self._console_size
        :return: 结果图片
        """
        img = self._console.make_screenshot()
        self._console_size[1], self._console_size[0] = img.shape[:2]
        return img

    def _click(self, x, y, random_range: int = 4, times: int = 1, internal: float = 0.05, delay: float = 0.5):
        """
        包装点击操作
        """
        self._console.tap(x, y, random_range, times, internal, delay)

    def _match_template_now(self, templ: TemplateLike, threshold: float = -1, clicked: bool = False,
                            delay: float = 1) -> tuple[bool, tuple[int, int]]:
        """
        包装匹配模板和点击模板坐标的代码逻辑
        :param templ: 模板类中的变量，来自Templates类
        :param threshold: 分数阈值
        :param clicked: 是否点击
        :param delay: 点击延迟
        :return: 是否匹配成功，最大分数坐标计算出的模板中心坐标
        """
        if threshold == -1:
            threshold = self._tm_threshold
        img = self._make_screenshot()
        result = cv2.matchTemplate(img, templ[1], cv2.TM_CCOEFF_NORMED)
        _, m, _, loc = cv2.minMaxLoc(result)
        loc = int(loc[0] + templ[1].shape[1] / 2), int(loc[1] + templ[1].shape[0] / 2)
        logger.debug(f"匹配模板: [{templ[0]}], m: {m}, loc: {loc}")
        if m > threshold:
            logger.debug(f"m: {m} > threshold: {threshold}, [{templ[0]}] 匹配成功")
            if clicked:
                logger.debug(f"点击 [{templ[0]}]")
                self._click(*loc, delay=delay)
        else:
            logger.debug(f"m: {m} <= threshold: {threshold}, [{templ[0]}] 匹配失败")
            if clicked:
                # 如果要点击却没匹配到就警告
                logger.warning(f"未能顺利匹配到需要点击的模板: [{templ[0]}]")
                # 抛出错误
                raise TemplateNotFoundError(templ)
        return m > threshold, loc

    def _is_at_home(self):
        """判断是否处于主界面"""
        origin_pn = self._console.adb_execute('shell dumpsys window | findstr mCurrentFocus').decode('utf-8')
        package_name = origin_pn.split()[-1].split('/')[0]
        return package_name == "com.mumu.launcher"

    def _back_to_home(self):
        """回到主界面"""
        self._console.adb_execute('shell input keyevent 3', "回到主界面")

    def _stop(self, package_name: str):
        """关闭指定应用"""
        self._console.adb_execute(f"shell am force-stop {package_name}", f"关闭应用: {package_name}")

    def run(self, index: int, course_name: str, delay: float = 5.0):
        """
        签到任务的执行入口
        :param index: 微信坐标列表的下标
        :param course_name: 课程名称
        :param delay: 操作延迟
        """
        # 每次登录一下，防止因为距离上一次签到过久超时邮箱自动退出而导致无法发送信息
        self._notifier.login()
        if not self._is_at_home():
            # 关闭微信，回到主界面
            logger.info("关闭微信并回到主界面")
            self._stop("com.tencent.mm")
            self._back_to_home()
            # 等待一下
            time.sleep(1)
        # 找到桌面的所有微信
        img = self._make_screenshot()
        result = cv2.matchTemplate(img, Template.SOFTWARE_WECHAT[1], cv2.TM_CCOEFF_NORMED)
        # 要保证每个微信图标只有一个匹配
        hs, ws = np.where(result > 0.8)
        locations = []
        hh, ww = Template.SOFTWARE_WECHAT[1].shape[:2]
        for h, w in zip(hs, ws):
            x = int(w + ww / 2)
            y = int(h + hh / 2)
            for xx, yy in locations:
                # 去掉和之前很接近的
                xd = abs(x - xx)
                yd = abs(y - yy)
                if xd + yd < 20:
                    break
            else:
                # 没有接近的
                locations.append([x, y])
        # 按从左到右排序，为了下一行尽量往后，给h加权多一点
        locations = sorted(locations, key=lambda x: x[0] + x[1] * 2)
        if len(locations) != len(self._email_list):
            # 统一长度
            min_len = min(len(locations), len(self._email_list))
            self._email_list = self._email_list[:min_len]
            locations = locations[:min_len]
        # 点击指定下标的微信图标
        lc = locations[index]
        email = self._email_list[index]
        logger.info(f"当前签到邮箱: [{email}], 当前签到课程: [{course_name}]")
        logger.debug(f"进入邮箱 [{email}] 所属微信并等待加载")
        self._click(*lc, delay=delay)
        # 执行签到
        try:
            is_check_in = self._check_in_now(email, delay)
        except NotLoggedInError:
            return
        img_now = self._make_screenshot()
        if is_check_in:
            logger.info(f"邮箱 [{email}] 所属微信号的 [{course_name}] 课程签到成功")
            self._notifier.send(*get_success_msg(course_name), to=email, image=img_now)
        else:
            logger.warning(f"邮箱 [{email}] 所属微信号的 [{course_name}] 课程未能顺利签到")
            self._notifier.send(*get_fail_msg(course_name), to=email, image=img_now)
            # 给主邮箱也发一份
            self._notifier.send(*get_developer_msg(f"邮箱 [{email}] 所属微信号的 [{course_name}] 课程未能顺利签到"),
                                to=self._main_email, image=img_now)
        if not self._is_at_home():
            # 关闭微信，回到主界面
            logger.info("关闭微信并回到主界面")
            self._stop("com.tencent.mm")
            self._back_to_home()
            # 等待一下
            time.sleep(1)

    def _check_in_now(self, email: str, delay: float = 5.0) -> bool:
        """
        执行签到操作
        :param email: 当前的通知邮箱
        :param delay: 操作延迟
        :return: 是否正常签到
        """
        # 处于微信应用内
        # 登录检测
        self._is_wechat_login(email)
        # 执行签到
        w, h = self._console_size
        logger.info("前往小程序界面")
        for _ in range(3):
            # 划多几下保证到达小程序界面
            self._console.swipe(int(w / 2), int(h * 0.1), int(w / 2), int(h * 0.9))
        # 进入小程序
        try:
            self._match_template_now(Template.WECHAT_XCX_HS, clicked=True, delay=delay)
        except TemplateNotFoundError:
            logger.warning("找不到hs小程序!")
            return False
        for _ in range(10):
            # 尝试五次
            self._click(450, 350, delay=delay/2)
            have_check_in, loc = self._match_template_now(Template.XCX_HS_CHECK_IN)
            if have_check_in:
                # 点击签到按钮
                logger.info("点击签到")
                self._click(*loc, delay=delay/2)
                have_confirm, loc = self._match_template_now(Template.XCX_HS_CONFIRM)
                if have_confirm:
                    # 有确认按钮说明没签到成功
                    logger.info("点击确认")
                    self._click(*loc, delay=delay/2)
                else:
                    # 没有确认可能就是签好了
                    break
        # 是否签到成功
        is_success, _ = self._match_template_now(Template.XCX_HS_SUCCESS)
        return is_success

    def _is_wechat_login(self, email: str):
        """
        检测微信是否登录, 不是则发送提示信息
        :param email: 当前的通知邮箱
        """
        logger.debug("检测微信是否登录")
        is_login, _ = self._match_template_now(Template.WECHAT_SEARCH)
        if not is_login:
            logger.warning("微信未登录")
            img = self._make_screenshot()
            self._notifier.send(*get_fail_msg("微信未登录"), to=email, image=img)
            raise NotLoggedInError
